package org.budo.weixin.work.finance;

import java.io.File;
import java.io.FileReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.security.PrivateKey;
import java.security.Security;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

import javax.crypto.Cipher;

import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.budo.support.lang.util.FileUtil;
import org.budo.support.lang.util.IoUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.lang.util.SystemUtil;
import org.budo.support.spring.io.util.ResourceUtil;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.tencent.wework.Finance;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Getter
@Setter
@Slf4j
public class BudoWorkWeixinFinanceSdkImpl implements BudoWorkWeixinFinanceSdk {
    private static final String[] LIBS_WIN = new String[] { "about.txt", "libcrypto-1_1-x64.dll", "libcurl-x64.dll", "libssl-1_1-x64.dll", "WeWorkFinanceSdk.dll" };

    private static final String[] LIBS_LINUX = new String[] { "about.txt", "libWeWorkFinanceSdk_Java.so" };

    private String corpId;

    private String secret;

    private String privateKey;

    public BudoWorkWeixinFinanceSdkImpl() {
        String path = System.getProperty("java.library.path");
        log.warn("#50 java.library.path={}", path);

        log.warn("#52 getCanonicalPath={}", this.libPath());
    }

    private String libPath() {
        String path = System.getProperty("java.library.path"); // java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
        if (null != path && path.contains("/usr/java/packages/lib/amd64:")) {
            return "/usr/java/packages/lib/amd64";
        }

        return FileUtil.getCanonicalPath(new File(".")); // /usr/local/data/install/taojin-wecom-web-launcher/taojin-wecom-web-launcher-assembly
    }

    private void assertLibrary() {
        if (SystemUtil.isWindows()) {
            this.assertLibrary_1(LIBS_WIN);
            return;
        }

        if (SystemUtil.isLinux()) {
            this.assertLibrary_1(LIBS_LINUX);
            return;
        }

        log.error("#38 not isWindows & isLinux, no loadLibrary");
    }

    private void assertLibrary_1(String[] libs) {
        String libPath = this.libPath();

        for (String lib : libs) {
            if (FileUtil.exists(libPath, lib)) {
                log.debug("#37 exists, canonicalPath={}, dll={}", libPath, lib);
                continue;
            }

            InputStream from = ResourceUtil.classPathResourceInputStream(lib);
            File to = new File(libPath, lib);

            IoUtil.write(from, to);

            log.debug("#49 after copy dll to {}", FileUtil.getCanonicalPath(to));
        }

        String path = System.getProperty("java.library.path");
        log.debug("#59 java.library.path={}", path);

        log.debug("#61 getCanonicalPath={}", libPath());
    }

    @Override
    public List<WeixinFinanceMessage> getChatData(Integer seq, Integer limit) {
        if (null == this.getCorpId() || this.getCorpId().isEmpty() //
                || null == this.getSecret() || this.getSecret().isEmpty() //
                || null == this.getPrivateKey() || this.getPrivateKey().isEmpty()) {
            throw new IllegalArgumentException("#71 corpId or secret or privateKey is empty, this=" + this);
        }

        if (this.getCorpId().startsWith("${") || this.getSecret().startsWith("${") || this.getPrivateKey().startsWith("${")) {
            throw new IllegalArgumentException("#110 corpId=" + this.getCorpId() + ", secret=" + this.getSecret() + ", privateKey=" + StringUtil.cutLeft(this.getPrivateKey(), 100) + ", this=" + this);
        }

        if (null == seq || seq < 0 || null == limit || limit < 1 || limit > 1000) {
            throw new IllegalArgumentException("#75 seq=" + seq + ", limit=" + limit + ", this=" + this);
        }

        this.assertLibrary();

        long newSdk = Finance.NewSdk();

        try {
            long initSdk = Finance.Init(newSdk, this.getCorpId(), this.getSecret());

            if (initSdk != 0) {
                Finance.DestroySdk(newSdk);
                log.error("#74 init sdk err ret " + initSdk);
                return null;
            }

            String chatData = this.getChatData(newSdk, seq, limit);

            List<WeixinFinanceMessage> decryptData_list = this.decryptData_list(newSdk, initSdk, chatData);
            return decryptData_list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            Finance.DestroySdk(newSdk); // 销毁释放
        }
    }

    private List<WeixinFinanceMessage> decryptData_list(long newSdk, long initSdk, String chatData) throws Exception {
        JSONObject jsonObject = JSON.parseObject(chatData);
        JSONArray chatdataArray = jsonObject.getJSONArray("chatdata");
        int chatdataArrayLen = chatdataArray.size();

        List<WeixinFinanceMessage> list = new ArrayList<WeixinFinanceMessage>();

        for (int i = 0; i < chatdataArrayLen; i++) {
            JSONObject chatdata = chatdataArray.getJSONObject(i);

            String decryptData = this.decryptData_each(newSdk, initSdk, chatdata);

            WeixinFinanceMessage message = new WeixinFinanceMessage(decryptData);
            message.setSeq(chatdata.getInteger("seq"));
            list.add(message);
        }

        // 结果非空
        if (!list.isEmpty()) {
            return list;
        }

        log.info("#167 decryptData_list, chatdataArrayLen=" + chatdataArrayLen + ", chatData=" + chatData);
        return list;
    }

    private String decryptData_each(long newSdk, long initSdk, JSONObject chatdata_0) throws Exception {
        String encrypt_random_key = chatdata_0.getString("encrypt_random_key");

        String encrypt_key = this.decryptRSA(encrypt_random_key); // 使用publickey_ver指定版本的私钥，使用RSA PKCS1算法对str1进行解密

        String encrypt_chat_msg = chatdata_0.getString("encrypt_chat_msg");

        long newSlice = Finance.NewSlice();

        long decryptData = Finance.DecryptData(newSdk, encrypt_key, encrypt_chat_msg, newSlice); // 解析密文

        if (initSdk != 0) {
            log.error("#66 getchatdata ret " + decryptData);
            Finance.FreeSlice(newSlice);
            return null;
        }

        String contentFromSlice = Finance.GetContentFromSlice(newSlice); // 获取slice内容
        Finance.FreeSlice(newSlice); // 释放slice

        return contentFromSlice;
    }

    public String getChatData(long sdk, long seq, long limit) {
        int timeout = 10; // 超时时间

        String proxy = null; // 代理
        String passwd = null; // 代理密码

        long newSlice = Finance.NewSlice();
        long getChatData = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, newSlice);
        if (getChatData != 0) {
            log.error("#122 Finance.GetChatData error, getChatData=" + getChatData);
            Finance.FreeSlice(newSlice);
            return null;
        }

        String contentFromSlice = Finance.GetContentFromSlice(newSlice);

        Finance.FreeSlice(newSlice);
        return contentFromSlice;
    }

    private String decryptRSA(String str) throws Exception {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");

        PrivateKey privateKey = this.privateKey();

        cipher.init(Cipher.DECRYPT_MODE, privateKey);

        byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(str));
        return new String(bytes, "UTF-8");
    }

    private PrivateKey privateKey() throws Exception {
        Reader reader = null;
        if (FileUtil.exists(this.getPrivateKey())) { // 是文件路径
            reader = new FileReader(new File(this.getPrivateKey()));
        } else if (StringUtil.startsWith(this.getPrivateKey(), "classpath:")) {
            InputStream inputStream = ResourceUtil.classPathResourceInputStream(this.getPrivateKey());
            reader = new InputStreamReader(inputStream);
        } else {
            reader = new StringReader(this.getPrivateKey().replaceAll("(\r\n|\r|\n|\n\r)", "\n")); // 直接是内容
        }

        PEMParser pemParser = new PEMParser(reader);
        Object privateObject = pemParser.readObject();

        if (privateObject instanceof PEMKeyPair) {
            PEMKeyPair pemKeyPair = (PEMKeyPair) privateObject;
            JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
            PrivateKey privKey = converter.getPrivateKey(pemKeyPair.getPrivateKeyInfo());

            pemParser.close(); // close it
            return privKey;
        }

        log.error("#135");
        return null;
    }
}